/* ====================================================================
 * Copyright (c) 1995-1998 The Apache Group.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * 4. The names "Apache Server" and "Apache Group" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For written permission, please contact
 *    apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * 6. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE APACHE GROUP OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Group and was originally based
 * on public domain software written at the National Center for
 * Supercomputing Applications, University of Illinois, Urbana-Champaign.
 * For more information on the Apache Group and the Apache HTTP server
 * project, please see <http://www.apache.org/>.
 *
 */

/*
 * http_protocol.c --- routines which directly communicate with the client.
 *
 * Code originally by Rob McCool; much redone by Robert S. Thau
 * and the Apache Group.
 */

#define CORE_PRIVATE
#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_protocol.h"
#include "http_main.h"
#include "http_request.h"
#include "http_vhost.h"
#include "http_log.h"           /* For errors detected in basic auth common
                                 * support code... */
#include "util_date.h"          /* For parseHTTPdate and BAD_DATE */
#include <stdarg.h>
#include "http_conf_globals.h"

#define SET_BYTES_SENT(r) \
  do { if (r->sent_bodyct) \
          ap_bgetopt (r->connection->client, BO_BYTECT, &r->bytes_sent); \
  } while (0)


static int parse_byterange(char *range, long clength, long *start, long *end)
{
    char *dash = strchr(range, '-');

    if (!dash)
        return 0;

    if ((dash == range)) {
        /* In the form "-5" */
        *start = clength - atol(dash + 1);
        *end = clength - 1;
    }
    else {
        *dash = '\0';
        dash++;
        *start = atol(range);
        if (*dash)
            *end = atol(dash);
        else                    /* "5-" */
            *end = clength - 1;
    }

    if (*start < 0)
	*start = 0;

    if (*end >= clength)
        *end = clength - 1;

    if (*start > *end)
	return 0;

    return (*start > 0 || *end < clength - 1);
}

static int internal_byterange(int, long *, request_rec *, const char **, long *,
                              long *);

API_EXPORT(int) ap_set_byterange(request_rec *r)
{
    const char *range, *if_range, *match;
    long range_start, range_end;

    if (!r->clength || r->assbackwards)
        return 0;

    /* Check for Range request-header (HTTP/1.1) or Request-Range for
     * backwards-compatibility with second-draft Luotonen/Franks
     * byte-ranges (e.g. Netscape Navigator 2-3).
     *
     * We support this form, with Request-Range, and (farther down) we
     * send multipart/x-byteranges instead of multipart/byteranges for
     * Request-Range based requests to work around a bug in Netscape
     * Navigator 2-3 and MSIE 3.
     */

    if (!(range = ap_table_get(r->headers_in, "Range")))
        range = ap_table_get(r->headers_in, "Request-Range");

    if (!range || strncasecmp(range, "bytes=", 6)) {
        return 0;
    }

    /* Check the If-Range header for Etag or Date */

    if ((if_range = ap_table_get(r->headers_in, "If-Range"))) {
        if (if_range[0] == '"') {
            if (!(match = ap_table_get(r->headers_out, "Etag")) ||
                (strcasecmp(if_range, match) != 0))
                return 0;
        }
        else if (!(match = ap_table_get(r->headers_out, "Last-Modified")) ||
                 (strcasecmp(if_range, match) != 0))
            return 0;
    }

    if (!strchr(range, ',')) {
        /* A single range */
        if (!parse_byterange(ap_pstrdup(r->pool, range + 6), r->clength,
                             &range_start, &range_end))
            return 0;

        r->byterange = 1;

        ap_table_setn(r->headers_out, "Content-Range",
	    ap_psprintf(r->pool, "bytes %ld-%ld/%ld",
		range_start, range_end, r->clength));
        ap_table_setn(r->headers_out, "Content-Length",
	    ap_psprintf(r->pool, "%ld", range_end - range_start + 1));
    }
    else {
        /* a multiple range */
        const char *r_range = ap_pstrdup(r->pool, range + 6);
        long tlength = 0;

        r->byterange = 2;
        r->boundary = ap_psprintf(r->pool, "%lx%lx",
				r->request_time, (long) getpid());
        while (internal_byterange(0, &tlength, r, &r_range, NULL, NULL));
        ap_table_setn(r->headers_out, "Content-Length",
	    ap_psprintf(r->pool, "%ld", tlength));
    }

    r->status = PARTIAL_CONTENT;
    r->range = range + 6;

    return 1;
}

API_EXPORT(int) ap_each_byterange(request_rec *r, long *offset, long *length)
{
    return internal_byterange(1, NULL, r, &r->range, offset, length);
}

/* If this function is called with realreq=1, it will spit out
 * the correct headers for a byterange chunk, and set offset and
 * length to the positions they should be.
 *
 * If it is called with realreq=0, it will add to tlength the length
 * it *would* have used with realreq=1.
 *
 * Either case will return 1 if it should be called again, and 0
 * when done.
 */
static int internal_byterange(int realreq, long *tlength, request_rec *r,
                              const char **r_range, long *offset, long *length)
{
    long range_start, range_end;
    char *range;

    if (!**r_range) {
        if (r->byterange > 1) {
            if (realreq)
                ap_rvputs(r, "\015\012--", r->boundary, "--\015\012", NULL);
            else
                *tlength += 4 + strlen(r->boundary) + 4;
        }
        return 0;
    }

    range = ap_getword(r->pool, r_range, ',');
    if (!parse_byterange(range, r->clength, &range_start, &range_end))
        /* Skip this one */
        return internal_byterange(realreq, tlength, r, r_range, offset,
                                  length);

    if (r->byterange > 1) {
        const char *ct = r->content_type ? r->content_type : ap_default_type(r);
        char ts[MAX_STRING_LEN];

        ap_snprintf(ts, sizeof(ts), "%ld-%ld/%ld", range_start, range_end,
                    r->clength);
        if (realreq)
            ap_rvputs(r, "\015\012--", r->boundary, "\015\012Content-type: ",
                   ct, "\015\012Content-range: bytes ", ts, "\015\012\015\012",
                   NULL);
        else
            *tlength += 4 + strlen(r->boundary) + 16 + strlen(ct) + 23 +
                        strlen(ts) + 4;
    }

    if (realreq) {
        *offset = range_start;
        *length = range_end - range_start + 1;
    }
    else {
        *tlength += range_end - range_start + 1;
    }
    return 1;
}

API_EXPORT(int) ap_set_content_length(request_rec *r, long clength)
{
    r->clength = clength;
    ap_table_setn(r->headers_out, "Content-Length", ap_psprintf(r->pool, "%ld", clength));
    return 0;
}

API_EXPORT(int) ap_set_keepalive(request_rec *r)
{
    int ka_sent = 0;
    int wimpy = ap_find_token(r->pool,
                           ap_table_get(r->headers_out, "Connection"), "close");
    const char *conn = ap_table_get(r->headers_in, "Connection");

    /* The following convoluted conditional determines whether or not
     * the current connection should remain persistent after this response
     * (a.k.a. HTTP Keep-Alive) and whether or not the output message
     * body should use the HTTP/1.1 chunked transfer-coding.  In English,
     *
     *   IF  we have not marked this connection as errored;
     *   and the response body has a defined length due to the status code
     *       being 304 or 204, the request method being HEAD, already
     *       having defined Content-Length or Transfer-Encoding: chunked, or
     *       the request version being HTTP/1.1 and thus capable of being set
     *       as chunked [we know the (r->chunked = 1) side-effect is ugly];
     *   and the server configuration enables keep-alive;
     *   and the server configuration has a reasonable inter-request timeout;
     *   and there is no maximum # requests or the max hasn't been reached;
     *   and the response status does not require a close;
     *   and the response generator has not already indicated close;
     *   and the client did not request non-persistence (Connection: close);
     *   and    we haven't been configured to ignore the buggy twit
     *       or they're a buggy twit coming through a HTTP/1.1 proxy
     *   and    the client is requesting an HTTP/1.0-style keep-alive
     *       or the client claims to be HTTP/1.1 compliant (perhaps a proxy);
     *   THEN we can be persistent, which requires more headers be output.
     *
     * Note that the condition evaluation order is extremely important.
     */
    if ((r->connection->keepalive != -1) &&
        ((r->status == HTTP_NOT_MODIFIED) ||
         (r->status == HTTP_NO_CONTENT) ||
         r->header_only ||
         ap_table_get(r->headers_out, "Content-Length") ||
         ap_find_last_token(r->pool,
                         ap_table_get(r->headers_out, "Transfer-Encoding"),
                         "chunked") ||
         ((r->proto_num >= HTTP_VERSION(1,1)) && (r->chunked = 1))) &&
        r->server->keep_alive &&
        (r->server->keep_alive_timeout > 0) &&
        ((r->server->keep_alive_max == 0) ||
         (r->server->keep_alive_max > r->connection->keepalives)) &&
        !status_drops_connection(r->status) &&
        !wimpy &&
        !ap_find_token(r->pool, conn, "close") &&
        (!ap_table_get(r->subprocess_env, "nokeepalive") ||
         ap_table_get(r->headers_in, "Via")) &&
        ((ka_sent = ap_find_token(r->pool, conn, "keep-alive")) ||
         (r->proto_num >= HTTP_VERSION(1,1)))
       ) {
        int left = r->server->keep_alive_max - r->connection->keepalives;

        r->connection->keepalive = 1;
        r->connection->keepalives++;

        /* If they sent a Keep-Alive token, send one back */
        if (ka_sent) {
            if (r->server->keep_alive_max)
		ap_table_setn(r->headers_out, "Keep-Alive",
		    ap_psprintf(r->pool, "timeout=%d, max=%d",
                            r->server->keep_alive_timeout, left));
            else
		ap_table_setn(r->headers_out, "Keep-Alive",
		    ap_psprintf(r->pool, "timeout=%d",
                            r->server->keep_alive_timeout));
            ap_table_mergen(r->headers_out, "Connection", "Keep-Alive");
        }

        return 1;
    }

    /* Otherwise, we need to indicate that we will be closing this
     * connection immediately after the current response.
     *
     * We only really need to send "close" to HTTP/1.1 clients, but we
     * always send it anyway, because a broken proxy may identify itself
     * as HTTP/1.0, but pass our request along with our HTTP/1.1 tag
     * to a HTTP/1.1 client. Better safe than sorry.
     */
    if (!wimpy)
	ap_table_mergen(r->headers_out, "Connection", "close");

    r->connection->keepalive = 0;

    return 0;
}

/*
 * Return the latest rational time from a request/mtime (modification time)
 * pair.  We return the mtime unless it's in the future, in which case we
 * return the current time.  We use the request time as a reference in order
 * to limit the number of calls to time().  We don't check for futurosity
 * unless the mtime is at least as new as the reference.
 */
API_EXPORT(time_t) ap_rationalize_mtime(request_rec *r, time_t mtime)
{
    time_t now;

    /* For all static responses, it's almost certain that the file was
     * last modified before the beginning of the request.  So there's
     * no reason to call time(NULL) again.  But if the response has been
     * created on demand, then it might be newer than the time the request
     * started.  In this event we really have to call time(NULL) again
     * so that we can give the clients the most accurate Last-Modified.  If we
     * were given a time in the future, we return the current time - the
     * Last-Modified can't be in the future.
     */
    now = (mtime < r->request_time) ? r->request_time : time(NULL);
    return (mtime > now) ? now : mtime;
}

API_EXPORT(int) ap_meets_conditions(request_rec *r)
{
    const char *etag = ap_table_get(r->headers_out, "ETag");
    const char *if_match, *if_modified_since, *if_unmodified, *if_nonematch;
    time_t mtime;

    /* Check for conditional requests --- note that we only want to do
     * this if we are successful so far and we are not processing a
     * subrequest or an ErrorDocument.
     *
     * The order of the checks is important, since ETag checks are supposed
     * to be more accurate than checks relative to the modification time.
     * However, not all documents are guaranteed to *have* ETags, and some
     * might have Last-Modified values w/o ETags, so this gets a little
     * complicated.
     */

    if (!is_HTTP_SUCCESS(r->status) || r->no_local_copy) {
        return OK;
    }

    mtime = (r->mtime != 0) ? r->mtime : time(NULL);

    /* If an If-Match request-header field was given
     * AND if our ETag does not match any of the entity tags in that field
     * AND the field value is not "*" (meaning match anything), then
     *     respond with a status of 412 (Precondition Failed).
     */
    if ((if_match = ap_table_get(r->headers_in, "If-Match")) != NULL) {
        if ((etag == NULL) ||
            ((if_match[0] != '*') && !ap_find_token(r->pool, if_match, etag))) {
            return HTTP_PRECONDITION_FAILED;
        }
    }
    else {
        /* Else if a valid If-Unmodified-Since request-header field was given
         * AND the requested resource has been modified since the time
         * specified in this field, then the server MUST
         *     respond with a status of 412 (Precondition Failed).
         */
        if_unmodified = ap_table_get(r->headers_in, "If-Unmodified-Since");
        if (if_unmodified != NULL) {
            time_t ius = ap_parseHTTPdate(if_unmodified);

            if ((ius != BAD_DATE) && (mtime > ius)) {
                return HTTP_PRECONDITION_FAILED;
            }
        }
    }

    /* If an If-None-Match request-header field was given
     * AND if our ETag matches any of the entity tags in that field
     * OR if the field value is "*" (meaning match anything), then
     *    if the request method was GET or HEAD, the server SHOULD
     *       respond with a 304 (Not Modified) response.
     *    For all other request methods, the server MUST
     *       respond with a status of 412 (Precondition Failed).
     */
    if_nonematch = ap_table_get(r->headers_in, "If-None-Match");
    if (if_nonematch != NULL) {
        int rstatus;

        if ((if_nonematch[0] == '*')
            || ((etag != NULL) && ap_find_token(r->pool, if_nonematch, etag))) {
            rstatus = (r->method_number == M_GET) ? HTTP_NOT_MODIFIED
                                                  : HTTP_PRECONDITION_FAILED;
            return rstatus;
        }
    }
    /* Else if a valid If-Modified-Since request-header field was given
     * AND it is a GET or HEAD request
     * AND the requested resource has not been modified since the time
     * specified in this field, then the server MUST
     *    respond with a status of 304 (Not Modified).
     * A date later than the server's current request time is invalid.
     */
    else if ((r->method_number == M_GET)
             && ((if_modified_since =
                  ap_table_get(r->headers_in, "If-Modified-Since")) != NULL)) {
        time_t ims = ap_parseHTTPdate(if_modified_since);

        if ((ims >= mtime) && (ims <= r->request_time)) {
            return HTTP_NOT_MODIFIED;
        }
    }
    return OK;
}

/*
 * Construct an entity tag (ETag) from resource information.  If it's a real
 * file, build in some of the file characteristics.  If the modification time
 * is newer than (request-time minus 1 second), mark the ETag as weak - it
 * could be modified again in as short an interval.  We rationalize the
 * modification time we're given to keep it from being in the future.
 */
API_EXPORT(void) ap_set_etag(request_rec *r)
{
    char *etag;
    char *weak;

    /*
     * Make an ETag header out of various pieces of information. We use
     * the last-modified date and, if we have a real file, the
     * length and inode number - note that this doesn't have to match
     * the content-length (i.e. includes), it just has to be unique
     * for the file.
     *
     * If the request was made within a second of the last-modified date,
     * we send a weak tag instead of a strong one, since it could
     * be modified again later in the second, and the validation
     * would be incorrect.
     */
    
    weak = (r->request_time - r->mtime > 1) ? "" : "W/";

    if (r->finfo.st_mode != 0) {
	etag = ap_psprintf(r->pool,
		    "%s\"%lx-%lx-%lx\"", weak,
                    (unsigned long) r->finfo.st_ino,
                    (unsigned long) r->finfo.st_size,
                    (unsigned long) r->mtime);
    }
    else {
        etag = ap_psprintf(r->pool, "%s\"%lx\"", weak,
                    (unsigned long) r->mtime);
    }

    ap_table_setn(r->headers_out, "ETag", etag);
}

/*
 * This function sets the Last-Modified output header field to the value
 * of the mtime field in the request structure - rationalized to keep it from
 * being in the future.
 */
API_EXPORT(void) ap_set_last_modified(request_rec *r)
{
    time_t mod_time = ap_rationalize_mtime(r, r->mtime);

    ap_table_setn(r->headers_out, "Last-Modified",
              ap_gm_timestr_822(r->pool, mod_time));
}

/* Get a line of protocol input, including any continuation lines
 * caused by MIME folding (or broken clients) if fold != 0, and place it
 * in the buffer s, of size n bytes, without the ending newline.
 *
 * Returns -1 on error, or the length of s.
 *
 * Note: Because bgets uses 1 char for newline and 1 char for NUL,
 *       the most we can get is (n - 2) actual characters if it
 *       was ended by a newline, or (n - 1) characters if the line
 *       length exceeded (n - 1).  So, if the result == (n - 1),
 *       then the actual input line exceeded the buffer length,
 *       and it would be a good idea for the caller to puke 400 or 414.
 */
static int getline(char *s, int n, BUFF *in, int fold)
{
    char *pos, next;
    int retval;
    int total = 0;

    pos = s;

    do {
        retval = ap_bgets(pos, n, in);     /* retval == -1 if error, 0 if EOF */

        if (retval <= 0)
            return ((retval < 0) && (total == 0)) ? -1 : total;

        /* retval is the number of characters read, not including NUL      */

        n -= retval;            /* Keep track of how much of s is full     */
        pos += (retval - 1);    /* and where s ends                        */
        total += retval;        /* and how long s has become               */

        if (*pos == '\n') {     /* Did we get a full line of input?        */
            *pos = '\0';
            --total;
            ++n;
        }
        else
            return total;       /* if not, input line exceeded buffer size */

        /* Continue appending if line folding is desired and
         * the last line was not empty and we have room in the buffer and
         * the next line begins with a continuation character.
         */
    } while (fold && (retval != 1) && (n > 1)
                  && (ap_blookc(&next, in) == 1)
                  && ((next == ' ') || (next == '\t')));

    return total;
}

/* parse_uri: break apart the uri
 * Side Effects:
 * - sets r->args to rest after '?' (or NULL if no '?')
 * - sets r->uri to request uri (without r->args part)
 * - sets r->hostname (if not set already) from request (scheme://host:port)
 */
CORE_EXPORT(void) ap_parse_uri(request_rec *r, const char *uri)
{
    int status = HTTP_OK;

    r->unparsed_uri = ap_pstrdup(r->pool, uri);

    if (r->method_number == M_CONNECT) {
	status = ap_parse_hostinfo_components(r->pool, uri, &r->parsed_uri);
    } else {
	/* Simple syntax Errors in URLs are trapped by parse_uri_components(). */
	status = ap_parse_uri_components(r->pool, uri, &r->parsed_uri);
    }

    if (is_HTTP_SUCCESS(status)) {
	/* if it has a scheme we may need to do absoluteURI vhost stuff */
	if (r->parsed_uri.scheme
	    && !strcasecmp(r->parsed_uri.scheme, ap_http_method(r))) {
	    r->hostname = r->parsed_uri.hostname;
	} else if (r->method_number == M_CONNECT) {
	    r->hostname = r->parsed_uri.hostname;
	}
	r->args = r->parsed_uri.query;
	r->uri = r->parsed_uri.path ? r->parsed_uri.path
				    : ap_pstrdup(r->pool, "/");
#if defined(__EMX__) || defined(WIN32)
	/* Handle path translations for OS/2 and plug security hole.
	 * This will prevent "http://www.wherever.com/..\..\/" from
	 * returning a directory for the root drive.
	 */
	{
	    char *x;

	    for (x = r->uri; (x = strchr(x, '\\')) != NULL; )
		*x = '/';
#ifndef WIN32   /* for OS/2 only: */
	    /* Fix OS/2 HPFS filename case problem. */
	    ap_str_tolower(r->uri);
#endif
	}
#endif  /* __EMX__ || WIN32 */
    }
    else {
	r->args = NULL;
	r->hostname = NULL;
	r->status = status;             /* set error status */
	r->uri = ap_pstrdup(r->pool, uri);
    }
}

static int read_request_line(request_rec *r)
{
    char l[HUGE_STRING_LEN];
    const char *ll = l, *uri;
    conn_rec *conn = r->connection;
    int major = 1, minor = 0;   /* Assume HTTP/1.0 if non-"HTTP" protocol */
    int len;

    /* Read past empty lines until we get a real request line,
     * a read error, the connection closes (EOF), or we timeout.
     *
     * We skip empty lines because browsers have to tack a CRLF on to the end
     * of POSTs to support old CERN webservers.  But note that we may not
     * have flushed any previous response completely to the client yet.
     * We delay the flush as long as possible so that we can improve
     * performance for clients that are pipelining requests.  If a request
     * is pipelined then we won't block during the (implicit) read() below.
     * If the requests aren't pipelined, then the client is still waiting
     * for the final buffer flush from us, and we will block in the implicit
     * read().  B_SAFEREAD ensures that the BUFF layer flushes if it will
     * have to block during a read.
     */
    ap_bsetflag(conn->client, B_SAFEREAD, 1);
    while ((len = getline(l, HUGE_STRING_LEN, conn->client, 0)) <= 0) {
        if ((len < 0) || ap_bgetflag(conn->client, B_EOF)) {
            ap_bsetflag(conn->client, B_SAFEREAD, 0);
            return 0;
        }
    }
    /* we've probably got something to do, ignore graceful restart requests */
#ifdef SIGUSR1
    signal(SIGUSR1, SIG_IGN);
#endif                          /* SIGUSR1 */
    ap_bsetflag(conn->client, B_SAFEREAD, 0);
    if (len == (HUGE_STRING_LEN - 1)) {
        ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server,
                    "request failed for %s, reason: URI too long",
            ap_get_remote_host(r->connection, r->per_dir_config, REMOTE_NAME));
	/* hack to deal with the HTTP_REQUEST_TIME_OUT setting up above: */
	if (r->status == HTTP_REQUEST_TIME_OUT) {
	    r->status = HTTP_OK;
	}
	r->request_time = time(NULL);
	ap_die (HTTP_REQUEST_URI_TOO_LARGE, r);
        return 0;
    }

    r->request_time = time(NULL);
    r->the_request = ap_pstrdup(r->pool, l);
    r->method = ap_getword_white(r->pool, &ll);
    uri = ap_getword_white(r->pool, &ll);

    /* Provide quick information about the request method as soon as known */
    if (!strcmp(r->method, "HEAD")) {
        r->header_only = 1;
        r->method_number = M_GET;
    }
    else if (!strcmp(r->method, "GET"))
        r->method_number = M_GET;
    else if (!strcmp(r->method, "POST"))
        r->method_number = M_POST;
    else if (!strcmp(r->method, "PUT"))
        r->method_number = M_PUT;
    else if (!strcmp(r->method, "DELETE"))
        r->method_number = M_DELETE;
    else if (!strcmp(r->method, "CONNECT"))
        r->method_number = M_CONNECT;
    else if (!strcmp(r->method, "OPTIONS"))
        r->method_number = M_OPTIONS;
    else if (!strcmp(r->method, "TRACE"))
        r->method_number = M_TRACE;
    else
        r->method_number = M_INVALID;   /* Will eventually croak. */

    ap_parse_uri(r, uri);

    r->assbackwards = (ll[0] == '\0');
    r->protocol = ap_pstrdup(r->pool, ll[0] ? ll : "HTTP/0.9");
    if (2 == sscanf(r->protocol, "HTTP/%u.%u", &major, &minor)
      && minor < HTTP_VERSION(1,0))	/* don't allow HTTP/0.1000 */
	r->proto_num = HTTP_VERSION(major, minor);
    else
	r->proto_num = HTTP_VERSION(1,0);

    return 1;
}

static void get_mime_headers(request_rec *r)
{
    conn_rec *c = r->connection;
    int len;
    char *value;
    char field[MAX_STRING_LEN];

    /*
     * Read header lines until we get the empty separator line, a read error,
     * the connection closes (EOF), or we timeout.
     */
    while ((len = getline(field, MAX_STRING_LEN, c->client, 1)) > 0) {
        char *copy = ap_palloc(r->pool, len + 1);
        memcpy(copy, field, len + 1);
	
	if (!(value = strchr(copy, ':'))) {     /* Find the colon separator */
	    /* if there's none, this request is screwed up.
	     * a hack to deal with how we set HTTP_REQUEST_TIME_OUT earlier.*/
	    if (r->status == HTTP_REQUEST_TIME_OUT)
		r->status = HTTP_OK;
	    
	    ap_die (HTTP_BAD_REQUEST, r);
	    return;
	}

        *value = '\0';
        ++value;
	/* XXX: RFC2068 defines only SP and HT as whitespace, this test is
	 * wrong... and so are many others probably.
	 */
        while (ap_isspace(*value))
            ++value;            /* Skip to start of value   */

	/* XXX: should strip trailing whitespace as well */

        ap_table_mergen(r->headers_in, copy, value);

	/* the header was too long; at the least we should skip extra data */
	if (len >= MAX_STRING_LEN - 1) { 
	    while ((len = getline(field, MAX_STRING_LEN, c->client, 1))
		    >= MAX_STRING_LEN - 1) {
		/* soak up the extra data */
	    }
	    if (len == 0) /* time to exit the larger loop as well */
		break;
	}
    }
}

request_rec *ap_read_request(conn_rec *conn)
{
    request_rec *r;
    int access_status;
    pool *p;

    p = ap_make_sub_pool(conn->pool);
    r = ap_pcalloc(p, sizeof(request_rec));
    r->pool            = p;
    r->connection      = conn;
    conn->server       = conn->base_server;
    r->server          = conn->server;

    conn->keptalive    = conn->keepalive == 1;
    conn->keepalive    = 0;

    conn->user         = NULL;
    conn->ap_auth_type    = NULL;

    r->headers_in      = ap_make_table(r->pool, 50);
    r->subprocess_env  = ap_make_table(r->pool, 50);
    r->headers_out     = ap_make_table(r->pool, 12);
    r->err_headers_out = ap_make_table(r->pool, 5);
    r->notes           = ap_make_table(r->pool, 5);

    r->request_config  = ap_create_request_config(r->pool);
    r->per_dir_config  = r->server->lookup_defaults;

    r->sent_bodyct     = 0;                      /* bytect isn't for body */

    r->read_length     = 0;
    r->read_body       = REQUEST_NO_BODY;

    r->status          = HTTP_REQUEST_TIME_OUT;  /* Until we get a request */
    r->the_request     = NULL;

    /* Get the request... */

#ifdef CHARSET_EBCDIC
    ap_bsetflag(r->connection->client, B_ASCII2EBCDIC|B_EBCDIC2ASCII, 1);
#endif /* CHARSET_EBCDIC */
    ap_keepalive_timeout("read request line", r);
    if (!read_request_line(r)) {
        ap_kill_timeout(r);
	if (r->status != HTTP_REQUEST_TIME_OUT) {
	    /* we must have had an error.*/
	    ap_log_transaction(r);
	}
        return NULL;
    }
    if (!r->assbackwards) {
        ap_hard_timeout("read request headers", r);
        get_mime_headers(r);
        if (r->status != HTTP_REQUEST_TIME_OUT) {/* we must have had an error.*/
	    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server,
		         "request failed for %s: error reading the headers",
		         ap_get_remote_host(r->connection, r->per_dir_config, 
					    REMOTE_NAME));
	    ap_log_transaction(r);
	    return NULL;
	}

    }
    ap_kill_timeout(r);

    r->status = HTTP_OK;                         /* Until further notice. */

    /* update what we think the virtual host is based on the headers we've
     * now read
     */
    ap_update_vhost_from_headers(r);

    /* we may have switched to another server */
    r->per_dir_config = r->server->lookup_defaults;

    conn->keptalive = 0;        /* We now have a request to play with */

    if ((access_status = ap_run_post_read_request(r))) {
        ap_die(access_status, r);
	ap_log_transaction(r);
        return NULL;
    }

    return r;
}

/*
 * A couple of other functions which initialize some of the fields of
 * a request structure, as appropriate for adjuncts of one kind or another
 * to a request in progress.  Best here, rather than elsewhere, since
 * *someone* has to set the protocol-specific fields...
 */

void ap_set_sub_req_protocol(request_rec *rnew, const request_rec *r)
{
    rnew->the_request     = r->the_request;  /* Keep original request-line */

    rnew->assbackwards    = 1;   /* Don't send headers from this. */
    rnew->no_local_copy   = 1;   /* Don't try to send USE_LOCAL_COPY for a
                                  * fragment. */
    rnew->method          = "GET";
    rnew->method_number   = M_GET;
    rnew->protocol        = "INCLUDED";

    rnew->status          = HTTP_OK;

    rnew->headers_in      = r->headers_in;
    rnew->subprocess_env  = ap_copy_table(rnew->pool, r->subprocess_env);
    rnew->headers_out     = ap_make_table(rnew->pool, 5);
    rnew->err_headers_out = ap_make_table(rnew->pool, 5);
    rnew->notes           = ap_make_table(rnew->pool, 5);

    rnew->read_length     = r->read_length;
    rnew->read_body       = REQUEST_NO_BODY;

    rnew->main = (request_rec *) r;
}

void ap_finalize_sub_req_protocol(request_rec *sub)
{
    SET_BYTES_SENT(sub->main);
}

/*
 * Support for the Basic authentication protocol, and a bit for Digest.
 */

API_EXPORT(void) ap_note_auth_failure(request_rec *r)
{
    if (!strcasecmp(ap_auth_type(r), "Basic"))
        ap_note_basic_auth_failure(r);
    else if (!strcasecmp(ap_auth_type(r), "Digest"))
        ap_note_digest_auth_failure(r);
}

API_EXPORT(void) ap_note_basic_auth_failure(request_rec *r)
{
    if (strcasecmp(ap_auth_type(r), "Basic"))
        ap_note_auth_failure(r);
    else
        ap_table_setn(r->err_headers_out,
                  r->proxyreq ? "Proxy-Authenticate" : "WWW-Authenticate",
                  ap_pstrcat(r->pool, "Basic realm=\"", ap_auth_name(r), "\"",
                          NULL));
}

API_EXPORT(void) ap_note_digest_auth_failure(request_rec *r)
{
    ap_table_setn(r->err_headers_out,
	    r->proxyreq ? "Proxy-Authenticate" : "WWW-Authenticate",
	    ap_psprintf(r->pool, "Digest realm=\"%s\", nonce=\"%lu\"",
		ap_auth_name(r), r->request_time));
}

API_EXPORT(int) ap_get_basic_auth_pw(request_rec *r, const char **pw)
{
    const char *auth_line = ap_table_get(r->headers_in,
                                      r->proxyreq ? "Proxy-Authorization"
                                                  : "Authorization");
    const char *t;

    if (!(t = ap_auth_type(r)) || strcasecmp(t, "Basic"))
        return DECLINED;

    if (!ap_auth_name(r)) {
        ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR,
		    r->server, "need AuthName: %s", r->uri);
        return SERVER_ERROR;
    }

    if (!auth_line) {
        ap_note_basic_auth_failure(r);
        return AUTH_REQUIRED;
    }

    if (strcasecmp(ap_getword(r->pool, &auth_line, ' '), "Basic")) {
        /* Client tried to authenticate using wrong auth scheme */
        ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server,
                    "client used wrong authentication scheme: %s", r->uri);
        ap_note_basic_auth_failure(r);
        return AUTH_REQUIRED;
    }

    t = ap_uudecode(r->pool, auth_line);
    /* Note that this allocation has to be made from r->connection->pool
     * because it has the lifetime of the connection.  The other allocations
     * are temporary and can be tossed away any time.
     */
    r->connection->user = ap_getword_nulls (r->connection->pool, &t, ':');
    r->connection->ap_auth_type = "Basic";

    *pw = t;

    return OK;
}

/* New Apache routine to map status codes into array indicies
 *  e.g.  100 -> 0,  101 -> 1,  200 -> 2 ...
 * The number of status lines must equal the value of RESPONSE_CODES (httpd.h)
 * and must be listed in order.
 */

static char *status_lines[] = {
    "100 Continue",
    "101 Switching Protocols",
#define LEVEL_200  2
    "200 OK",
    "201 Created",
    "202 Accepted",
    "203 Non-Authoritative Information",
    "204 No Content",
    "205 Reset Content",
    "206 Partial Content",
#define LEVEL_300  9
    "300 Multiple Choices",
    "301 Moved Permanently",
    "302 Moved Temporarily",
    "303 See Other",
    "304 Not Modified",
    "305 Use Proxy",
#define LEVEL_400 15
    "400 Bad Request",
    "401 Authorization Required",
    "402 Payment Required",
    "403 Forbidden",
    "404 Not Found",
    "405 Method Not Allowed",
    "406 Not Acceptable",
    "407 Proxy Authentication Required",
    "408 Request Time-out",
    "409 Conflict",
    "410 Gone",
    "411 Length Required",
    "412 Precondition Failed",
    "413 Request Entity Too Large",
    "414 Request-URI Too Large",
    "415 Unsupported Media Type",
#define LEVEL_500 31
    "500 Internal Server Error",
    "501 Method Not Implemented",
    "502 Bad Gateway",
    "503 Service Temporarily Unavailable",
    "504 Gateway Time-out",
    "505 HTTP Version Not Supported",
    "506 Variant Also Varies"
};

/* The index is found by its offset from the x00 code of each level.
 * Although this is fast, it will need to be replaced if some nutcase
 * decides to define a high-numbered code before the lower numbers.
 * If that sad event occurs, replace the code below with a linear search
 * from status_lines[shortcut[i]] to status_lines[shortcut[i+1]-1];
 */
API_EXPORT(int) ap_index_of_response(int status)
{
    static int shortcut[6] = {0, LEVEL_200, LEVEL_300, LEVEL_400,
    LEVEL_500, RESPONSE_CODES};
    int i, pos;

    if (status < 100)           /* Below 100 is illegal for HTTP status */
        return LEVEL_500;

    for (i = 0; i < 5; i++) {
        status -= 100;
        if (status < 100) {
            pos = (status + shortcut[i]);
            if (pos < shortcut[i + 1])
                return pos;
            else
                return LEVEL_500;       /* status unknown (falls in gap) */
        }
    }
    return LEVEL_500;           /* 600 or above is also illegal */
}

/* Send a single HTTP header field to the client.  Note that this function
 * is used in calls to table_do(), so their interfaces are co-dependent.
 * In other words, don't change this one without checking table_do in alloc.c.
 * It returns true unless there was a write error of some kind.
 */
API_EXPORT_NONSTD(int) ap_send_header_field(request_rec *r,
    const char *fieldname, const char *fieldval)
{
    return (0 < ap_bvputs(r->connection->client,
                       fieldname, ": ", fieldval, "\015\012", NULL));
}

API_EXPORT(void) ap_basic_http_header(request_rec *r)
{
    char *protocol;
#ifdef CHARSET_EBCDIC
    int convert = ap_bgetflag(r->connection->client, B_EBCDIC2ASCII);
#endif /*CHARSET_EBCDIC*/

    if (r->assbackwards)
        return;

    if (!r->status_line)
        r->status_line = status_lines[ap_index_of_response(r->status)];

    /* mod_proxy is only HTTP/1.0, so avoid sending HTTP/1.1 error response;
     * kluge around broken browsers when indicated by force-response-1.0
     */
    if (r->proxyreq
        || (r->proto_num == HTTP_VERSION(1,0)
            && ap_table_get(r->subprocess_env, "force-response-1.0"))) {

        protocol = "HTTP/1.0";
        r->connection->keepalive = -1;
    }
    else
        protocol = SERVER_PROTOCOL;

#ifdef CHARSET_EBCDIC
    ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, 1);
#endif /*CHARSET_EBCDIC*/

    /* Output the HTTP/1.x Status-Line and the Date and Server fields */

    ap_bvputs(r->connection->client,
           protocol, " ", r->status_line, "\015\012", NULL);

    ap_send_header_field(r, "Date", ap_gm_timestr_822(r->pool, r->request_time));
    ap_send_header_field(r, "Server", ap_get_server_version());

    ap_table_unset(r->headers_out, "Date");        /* Avoid bogosity */
    ap_table_unset(r->headers_out, "Server");
#ifdef CHARSET_EBCDIC
    if (!convert)
        ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, convert);
#endif /*CHARSET_EBCDIC*/
}

/* Navigator versions 2.x, 3.x and 4.0 betas up to and including 4.0b2
 * have a header parsing bug.  If the terminating \r\n occur starting
 * at offset 256, 257 or 258 of output then it will not properly parse
 * the headers.  Curiously it doesn't exhibit this problem at 512, 513.
 * We are guessing that this is because their initial read of a new request
 * uses a 256 byte buffer, and subsequent reads use a larger buffer.
 * So the problem might exist at different offsets as well.
 *
 * This should also work on keepalive connections assuming they use the
 * same small buffer for the first read of each new request.
 *
 * At any rate, we check the bytes written so far and, if we are about to
 * tickle the bug, we instead insert a bogus padding header.  Since the bug
 * manifests as a broken image in Navigator, users blame the server.  :(
 * It is more expensive to check the User-Agent than it is to just add the
 * bytes, so we haven't used the BrowserMatch feature here.
 */
static void terminate_header(BUFF *client)
{
    long int bs;

    ap_bgetopt(client, BO_BYTECT, &bs);
    if (bs >= 255 && bs <= 257)
        ap_bputs("X-Pad: avoid browser bug\015\012", client);

    ap_bputs("\015\012", client);  /* Send the terminating empty line */
}

/* Build the Allow field-value from the request handler method mask.
 * Note that we always allow TRACE, since it is handled below.
 */
static char *make_allow(request_rec *r)
{
    return 2 + ap_pstrcat(r->pool,
                       (r->allowed & (1 << M_GET)) ? ", GET, HEAD" : "",
                       (r->allowed & (1 << M_POST)) ? ", POST" : "",
                       (r->allowed & (1 << M_PUT)) ? ", PUT" : "",
                       (r->allowed & (1 << M_DELETE)) ? ", DELETE" : "",
                       (r->allowed & (1 << M_OPTIONS)) ? ", OPTIONS" : "",
                       ", TRACE",
                       NULL);
}

int ap_send_http_trace(request_rec *r)
{
    int rv;

    /* Get the original request */
    while (r->prev)
        r = r->prev;

    if ((rv = ap_setup_client_block(r, REQUEST_NO_BODY)))
        return rv;

    ap_hard_timeout("send TRACE", r);

    r->content_type = "message/http";
    ap_send_http_header(r);

    /* Now we recreate the request, and echo it back */

    ap_rvputs(r, r->the_request, "\015\012", NULL);

    ap_table_do((int (*) (void *, const char *, const char *)) ap_send_header_field,
             (void *) r, r->headers_in, NULL);
    ap_bputs("\015\012", r->connection->client);

    ap_kill_timeout(r);
    return OK;
}

int ap_send_http_options(request_rec *r)
{
    const long int zero = 0L;

    if (r->assbackwards)
        return DECLINED;

    ap_hard_timeout("send OPTIONS", r);

    ap_basic_http_header(r);

    ap_table_setn(r->headers_out, "Content-Length", "0");
    ap_table_setn(r->headers_out, "Allow", make_allow(r));
    ap_set_keepalive(r);

    ap_table_do((int (*) (void *, const char *, const char *)) ap_send_header_field,
             (void *) r, r->headers_out, NULL);

    terminate_header(r->connection->client);

    ap_kill_timeout(r);
    ap_bsetopt(r->connection->client, BO_BYTECT, &zero);

    return OK;
}

/*
 * Here we try to be compatible with clients that want multipart/x-byteranges
 * instead of multipart/byteranges (also see above), as per HTTP/1.1. We
 * look for the Request-Range header (e.g. Netscape 2 and 3) as an indication
 * that the browser supports an older protocol. We also check User-Agent
 * for Microsoft Internet Explorer 3, which needs this as well.
 */
static int use_range_x(request_rec *r)
{
    const char *ua;
    return (ap_table_get(r->headers_in, "Request-Range") ||
            ((ua = ap_table_get(r->headers_in, "User-Agent"))
             && strstr(ua, "MSIE 3")));
}

API_EXPORT(void) ap_send_http_header(request_rec *r)
{
    int i;
    const long int zero = 0L;
#ifdef CHARSET_EBCDIC
    int convert = ap_bgetflag(r->connection->client, B_EBCDIC2ASCII);
#endif /*CHARSET_EBCDIC*/

    if (r->assbackwards) {
        if (!r->main)
            ap_bsetopt(r->connection->client, BO_BYTECT, &zero);
        r->sent_bodyct = 1;
        return;
    }

    /*
     * Now that we are ready to send a response, we need to combine the two
     * header field tables into a single table.  If we don't do this, our
     * later attempts to set or unset a given fieldname might be bypassed.
     */
    if (!ap_is_empty_table(r->err_headers_out))
        r->headers_out = ap_overlay_tables(r->pool, r->err_headers_out,
                                        r->headers_out);

    ap_hard_timeout("send headers", r);

    ap_basic_http_header(r);

#ifdef CHARSET_EBCDIC
    ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, 1);
#endif /*CHARSET_EBCDIC*/

    ap_set_keepalive(r);

    if (r->chunked) {
        ap_table_mergen(r->headers_out, "Transfer-Encoding", "chunked");
        ap_table_unset(r->headers_out, "Content-Length");
    }

    if (r->byterange > 1)
        ap_table_setn(r->headers_out, "Content-Type",
                  ap_pstrcat(r->pool, "multipart", use_range_x(r) ? "/x-" : "/",
                          "byteranges; boundary=", r->boundary, NULL));
    else if (r->content_type)
        ap_table_setn(r->headers_out, "Content-Type", r->content_type);
    else
        ap_table_setn(r->headers_out, "Content-Type", ap_default_type(r));

    if (r->content_encoding)
        ap_table_setn(r->headers_out, "Content-Encoding", r->content_encoding);

    if (r->content_languages && r->content_languages->nelts) {
        for (i = 0; i < r->content_languages->nelts; ++i) {
            ap_table_mergen(r->headers_out, "Content-Language",
                        ((char **) (r->content_languages->elts))[i]);
        }
    }
    else if (r->content_language)
        ap_table_setn(r->headers_out, "Content-Language", r->content_language);

    /*
     * Control cachability for non-cachable responses if not already set by
     * some other part of the server configuration.
     */
    if (r->no_cache && !ap_table_get(r->headers_out, "Expires"))
        ap_table_addn(r->headers_out, "Expires",
                  ap_gm_timestr_822(r->pool, r->request_time));

    /* Send the entire table of header fields, terminated by an empty line. */

    ap_table_do((int (*) (void *, const char *, const char *)) ap_send_header_field,
             (void *) r, r->headers_out, NULL);

    terminate_header(r->connection->client);

    ap_kill_timeout(r);

    ap_bsetopt(r->connection->client, BO_BYTECT, &zero);
    r->sent_bodyct = 1;         /* Whatever follows is real body stuff... */

    /* Set buffer flags for the body */
    if (r->chunked)
        ap_bsetflag(r->connection->client, B_CHUNK, 1);
#ifdef CHARSET_EBCDIC
    if (!convert)
        ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, convert);
#endif /*CHARSET_EBCDIC*/
}

/* finalize_request_protocol is called at completion of sending the
 * response.  It's sole purpose is to send the terminating protocol
 * information for any wrappers around the response message body
 * (i.e., transfer encodings).  It should have been named finalize_response.
 */
void ap_finalize_request_protocol(request_rec *r)
{
    if (r->chunked && !r->connection->aborted) {
        /*
         * Turn off chunked encoding --- we can only do this once.
         */
        r->chunked = 0;
        ap_bsetflag(r->connection->client, B_CHUNK, 0);

        ap_soft_timeout("send ending chunk", r);
        ap_bputs("0\015\012", r->connection->client);
        /* If we had footer "headers", we'd send them now */
        ap_bputs("\015\012", r->connection->client);
        ap_kill_timeout(r);
    }
}

/* Here we deal with getting the request message body from the client.
 * Whether or not the request contains a body is signaled by the presence
 * of a non-zero Content-Length or by a Transfer-Encoding: chunked.
 *
 * Note that this is more complicated than it was in Apache 1.1 and prior
 * versions, because chunked support means that the module does less.
 *
 * The proper procedure is this:
 *
 * 1. Call setup_client_block() near the beginning of the request
 *    handler. This will set up all the necessary properties, and will
 *    return either OK, or an error code. If the latter, the module should
 *    return that error code. The second parameter selects the policy to
 *    apply if the request message indicates a body, and how a chunked
 *    transfer-coding should be interpreted. Choose one of
 *
 *    REQUEST_NO_BODY          Send 413 error if message has any body
 *    REQUEST_CHUNKED_ERROR    Send 411 error if body without Content-Length
 *    REQUEST_CHUNKED_DECHUNK  If chunked, remove the chunks for me.
 *    REQUEST_CHUNKED_PASS     Pass the chunks to me without removal.
 *
 *    In order to use the last two options, the caller MUST provide a buffer
 *    large enough to hold a chunk-size line, including any extensions.
 *
 * 2. When you are ready to read a body (if any), call should_client_block().
 *    This will tell the module whether or not to read input. If it is 0,
 *    the module should assume that there is no message body to read.
 *    This step also sends a 100 Continue response to HTTP/1.1 clients,
 *    so should not be called until the module is *definitely* ready to
 *    read content. (otherwise, the point of the 100 response is defeated).
 *    Never call this function more than once.
 *
 * 3. Finally, call get_client_block in a loop. Pass it a buffer and its size.
 *    It will put data into the buffer (not necessarily a full buffer), and
 *    return the length of the input block. When it is done reading, it will
 *    return 0 if EOF, or -1 if there was an error.
 *    If an error occurs on input, we force an end to keepalive.
 */

API_EXPORT(int) ap_setup_client_block(request_rec *r, int read_policy)
{
    const char *tenc = ap_table_get(r->headers_in, "Transfer-Encoding");
    const char *lenp = ap_table_get(r->headers_in, "Content-Length");

    r->read_body = read_policy;
    r->read_chunked = 0;
    r->remaining = 0;

    if (tenc) {
        if (strcasecmp(tenc, "chunked")) {
            ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server,
                        "Unknown Transfer-Encoding %s", tenc);
            return HTTP_NOT_IMPLEMENTED;
        }
        if (r->read_body == REQUEST_CHUNKED_ERROR) {
            ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server,
                        "chunked Transfer-Encoding forbidden: %s", r->uri);
            return (lenp) ? HTTP_BAD_REQUEST : HTTP_LENGTH_REQUIRED;
        }

        r->read_chunked = 1;
    }
    else if (lenp) {
        const char *pos = lenp;

        while (ap_isdigit(*pos) || ap_isspace(*pos))
            ++pos;
        if (*pos != '\0') {
            ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server,
                        "Invalid Content-Length %s", lenp);
            return HTTP_BAD_REQUEST;
        }

        r->remaining = atol(lenp);
    }

    if ((r->read_body == REQUEST_NO_BODY) &&
        (r->read_chunked || (r->remaining > 0))) {
        ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server,
                    "%s with body is not allowed for %s", r->method, r->uri);
        return HTTP_REQUEST_ENTITY_TOO_LARGE;
    }

    return OK;
}

API_EXPORT(int) ap_should_client_block(request_rec *r)
{
    /* First check if we have already read the request body */

    if (r->read_length || (!r->read_chunked && (r->remaining <= 0)))
        return 0;

    if (r->proto_num >= HTTP_VERSION(1,1)) {
        /* sending 100 Continue interim response */
        ap_bvputs(r->connection->client,
               SERVER_PROTOCOL, " ", status_lines[0], "\015\012\015\012",
               NULL);
        ap_bflush(r->connection->client);
    }

    return 1;
}

static long get_chunk_size(char *b)
{
    long chunksize = 0;

    while (isxdigit(*b)) {
        int xvalue = 0;

        if (*b >= '0' && *b <= '9')
            xvalue = *b - '0';
        else if (*b >= 'A' && *b <= 'F')
            xvalue = *b - 'A' + 0xa;
        else if (*b >= 'a' && *b <= 'f')
            xvalue = *b - 'a' + 0xa;

        chunksize = (chunksize << 4) | xvalue;
        ++b;
    }

    return chunksize;
}

/* get_client_block is called in a loop to get the request message body.
 * This is quite simple if the client includes a content-length
 * (the normal case), but gets messy if the body is chunked. Note that
 * r->remaining is used to maintain state across calls and that
 * r->read_length is the total number of bytes given to the caller
 * across all invocations.  It is messy because we have to be careful not
 * to read past the data provided by the client, since these reads block.
 * Returns 0 on End-of-body, -1 on error or premature chunk end.
 *
 * Reading the chunked encoding requires a buffer size large enough to
 * hold a chunk-size line, including any extensions. For now, we'll leave
 * that to the caller, at least until we can come up with a better solution.
 */
API_EXPORT(long) ap_get_client_block(request_rec *r, char *buffer, int bufsiz)
{
    int c;
    long len_read, len_to_read;
    long chunk_start = 0;

    if (!r->read_chunked) {     /* Content-length read */
        len_to_read = (r->remaining > bufsiz) ? bufsiz : r->remaining;
        len_read = ap_bread(r->connection->client, buffer, len_to_read);
        if (len_read <= 0) {
            if (len_read < 0)
                r->connection->keepalive = -1;
            return len_read;
        }
        r->read_length += len_read;
        r->remaining -= len_read;
        return len_read;
    }

    /*
     * Handle chunked reading Note: we are careful to shorten the input
     * bufsiz so that there will always be enough space for us to add a CRLF
     * (if necessary).
     */
    if (r->read_body == REQUEST_CHUNKED_PASS)
        bufsiz -= 2;
    if (bufsiz <= 0)
        return -1;              /* Cannot read chunked with a small buffer */

    if (r->remaining == 0) {    /* Start of new chunk */

        chunk_start = getline(buffer, bufsiz, r->connection->client, 0);
        if ((chunk_start <= 0) || (chunk_start >= (bufsiz - 1))
            || !isxdigit(*buffer)) {
            r->connection->keepalive = -1;
            return -1;
        }

        len_to_read = get_chunk_size(buffer);

        if (len_to_read == 0) { /* Last chunk indicated, get footers */
            if (r->read_body == REQUEST_CHUNKED_DECHUNK) {
                get_mime_headers(r);
                ap_snprintf(buffer, bufsiz, "%ld", r->read_length);
                ap_table_unset(r->headers_in, "Transfer-Encoding");
                ap_table_setn(r->headers_in, "Content-Length",
                    ap_pstrdup(r->pool, buffer));
                return 0;
            }
            r->remaining = -1;  /* Indicate footers in-progress */
        }
        else {
            r->remaining = len_to_read;
        }
        if (r->read_body == REQUEST_CHUNKED_PASS) {
            buffer[chunk_start++] = CR; /* Restore chunk-size line end  */
            buffer[chunk_start++] = LF;
            buffer += chunk_start;      /* and pass line on to caller   */
            bufsiz -= chunk_start;
        }
        else {
            /* REQUEST_CHUNKED_DECHUNK -- do not include the length of the
             * header in the return value
             */
            chunk_start = 0;
        }
    }
                                /* When REQUEST_CHUNKED_PASS, we are */
    if (r->remaining == -1) {   /* reading footers until empty line  */
        len_read = chunk_start;

        while ((bufsiz > 1) && ((len_read =
                  getline(buffer, bufsiz, r->connection->client, 1)) > 0)) {

            if (len_read != (bufsiz - 1)) {
                buffer[len_read++] = CR;        /* Restore footer line end  */
                buffer[len_read++] = LF;
            }
            chunk_start += len_read;
            buffer += len_read;
            bufsiz -= len_read;
        }
        if (len_read < 0) {
            r->connection->keepalive = -1;
            return -1;
        }

        if (len_read == 0) {    /* Indicates an empty line */
            buffer[0] = CR;
            buffer[1] = LF;
            chunk_start += 2;
            r->remaining = -2;
        }
        r->read_length += chunk_start;
        return chunk_start;
    }
                                /* When REQUEST_CHUNKED_PASS, we     */
    if (r->remaining == -2) {   /* finished footers when last called */
        r->remaining = 0;       /* so now we must signal EOF         */
        return 0;
    }

    /* Otherwise, we are in the midst of reading a chunk of data */

    len_to_read = (r->remaining > bufsiz) ? bufsiz : r->remaining;

    len_read = ap_bread(r->connection->client, buffer, len_to_read);
    if (len_read <= 0) {
        r->connection->keepalive = -1;
        return -1;
    }

    r->remaining -= len_read;

    if (r->remaining == 0) {    /* End of chunk, get trailing CRLF */
        if ((c = ap_bgetc(r->connection->client)) == CR) {
            c = ap_bgetc(r->connection->client);
        }
        if (c != LF) {
            r->connection->keepalive = -1;
            return -1;
        }
        if (r->read_body == REQUEST_CHUNKED_PASS) {
            buffer[len_read++] = CR;
            buffer[len_read++] = LF;
        }
    }
    r->read_length += (chunk_start + len_read);

    return (chunk_start + len_read);
}

/* In HTTP/1.1, any method can have a body.  However, most GET handlers
 * wouldn't know what to do with a request body if they received one.
 * This helper routine tests for and reads any message body in the request,
 * simply discarding whatever it receives.  We need to do this because
 * failing to read the request body would cause it to be interpreted
 * as the next request on a persistent connection.
 *
 * Since we return an error status if the request is malformed, this
 * routine should be called at the beginning of a no-body handler, e.g.,
 *
 *    if ((retval = discard_request_body(r)) != OK)
 *        return retval;
 */
API_EXPORT(int) ap_discard_request_body(request_rec *r)
{
    int rv;

    if ((rv = ap_setup_client_block(r, REQUEST_CHUNKED_PASS)))
        return rv;

    if (ap_should_client_block(r)) {
        char dumpbuf[HUGE_STRING_LEN];

        ap_hard_timeout("reading request body", r);
        while ((rv = ap_get_client_block(r, dumpbuf, HUGE_STRING_LEN)) > 0)
            continue;
        ap_kill_timeout(r);

        if (rv < 0)
            return HTTP_BAD_REQUEST;
    }
    return OK;
}

/*
 * Send the body of a response to the client.
 */
API_EXPORT(long) ap_send_fd(FILE *f, request_rec *r)
{
    return ap_send_fd_length(f, r, -1);
}

API_EXPORT(long) ap_send_fd_length(FILE *f, request_rec *r, long length)
{
    char buf[IOBUFSIZE];
    long total_bytes_sent = 0;
    register int n, w, o, len;

    if (length == 0)
        return 0;

    ap_soft_timeout("send body", r);

    while (!r->connection->aborted) {
        if ((length > 0) && (total_bytes_sent + IOBUFSIZE) > length)
            len = length - total_bytes_sent;
        else
            len = IOBUFSIZE;

        while ((n = fread(buf, sizeof(char), len, f)) < 1
               && ferror(f) && errno == EINTR && !r->connection->aborted)
            continue;

        if (n < 1) {
            break;
        }
        o = 0;

        while (n && !r->connection->aborted) {
            w = ap_bwrite(r->connection->client, &buf[o], n);
            if (w > 0) {
                ap_reset_timeout(r);   /* reset timeout after successful write */
		total_bytes_sent += w;
                n -= w;
                o += w;
            }
            else if (w < 0) {
                if (r->connection->aborted)
                    break;
                else if (errno == EAGAIN)
                    continue;
                else {
                    ap_log_error(APLOG_MARK, APLOG_INFO, r->server,
                     "%s client stopped connection before send body completed",
                                ap_get_remote_host(r->connection,
                                                r->per_dir_config,
                                                REMOTE_NAME));
                    ap_bsetflag(r->connection->client, B_EOUT, 1);
                    r->connection->aborted = 1;
                    break;
                }
            }
        }
    }

    ap_kill_timeout(r);
    SET_BYTES_SENT(r);
    return total_bytes_sent;
}

/*
 * Send the body of a response to the client.
 */
API_EXPORT(long) ap_send_fb(BUFF *fb, request_rec *r)
{
    return ap_send_fb_length(fb, r, -1);
}

API_EXPORT(long) ap_send_fb_length(BUFF *fb, request_rec *r, long length)
{
    char buf[IOBUFSIZE];
    long total_bytes_sent = 0;
    register int n, w, o, len, fd;
    fd_set fds;

    if (length == 0)
        return 0;

    /* Make fb unbuffered and non-blocking */
    ap_bsetflag(fb, B_RD, 0);
    ap_bnonblock(fb, B_RD);
    fd = ap_bfileno(fb, B_RD);
#ifndef WIN32
    if (fd >= FD_SETSIZE) {
	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, NULL,
	    "send body: filedescriptor (%u) larger than FD_SETSIZE (%u) "
	    "found, you probably need to rebuild Apache with a "
	    "larger FD_SETSIZE", fd, FD_SETSIZE);
	return 0;
    }
#endif

    ap_soft_timeout("send body", r);

    FD_ZERO(&fds);
    while (!r->connection->aborted) {
#ifdef NDELAY_PIPE_RETURNS_ZERO
	/* Contributed by dwd@bell-labs.com for UTS 2.1.2, where the fcntl */
	/*   O_NDELAY flag causes read to return 0 when there's nothing */
	/*   available when reading from a pipe.  That makes it tricky */
	/*   to detect end-of-file :-(.  This stupid bug is even documented */
	/*   in the read(2) man page where it says that everything but */
	/*   pipes return -1 and EAGAIN.  That makes it a feature, right? */
	int afterselect = 0;
#endif
        if ((length > 0) && (total_bytes_sent + IOBUFSIZE) > length)
            len = length - total_bytes_sent;
        else
            len = IOBUFSIZE;

        do {
            n = ap_bread(fb, buf, len);
#ifdef NDELAY_PIPE_RETURNS_ZERO
	    if ((n > 0) || (n == 0 && afterselect))
		break;
#else
            if (n >= 0)
                break;
#endif
            if (r->connection->aborted)
                break;
            if (n < 0 && errno != EAGAIN)
                break;
            /* we need to block, so flush the output first */
            ap_bflush(r->connection->client);
            if (r->connection->aborted)
                break;
            FD_SET(fd, &fds);
            /*
             * we don't care what select says, we might as well loop back
             * around and try another read
             */
            ap_select(fd + 1, &fds, NULL, NULL, NULL);
#ifdef NDELAY_PIPE_RETURNS_ZERO
	    afterselect = 1;
#endif
        } while (!r->connection->aborted);

        if (n < 1 || r->connection->aborted) {
            break;
        }

        o = 0;
        total_bytes_sent += n;

        while (n && !r->connection->aborted) {
            w = ap_bwrite(r->connection->client, &buf[o], n);
            if (w > 0) {
                ap_reset_timeout(r);       /* reset timeout after successful
                                         * write */
                n -= w;
                o += w;
            }
            else if (w < 0) {
                if (r->connection->aborted)
                    break;
                else if (errno == EAGAIN)
                    continue;
                else {
                    ap_log_error(APLOG_MARK, APLOG_INFO, r->server,
                     "%s client stopped connection before send body completed",
                                ap_get_remote_host(r->connection,
                                                r->per_dir_config,
                                                REMOTE_NAME));
                    ap_bsetflag(r->connection->client, B_EOUT, 1);
                    r->connection->aborted = 1;
                    break;
                }
            }
        }
    }

    ap_kill_timeout(r);
    SET_BYTES_SENT(r);
    return total_bytes_sent;
}



/* The code writes MMAP_SEGMENT_SIZE bytes at a time.  This is due to Apache's
 * timeout model, which is a timeout per-write rather than a time for the
 * entire transaction to complete.  Essentially this should be small enough
 * so that in one Timeout period, your slowest clients should be reasonably
 * able to receive this many bytes.
 *
 * To take advantage of zero-copy TCP under Solaris 2.6 this should be a
 * multiple of 16k.  (And you need a SunATM2.0 network card.)
 */
#ifndef MMAP_SEGMENT_SIZE
#define MMAP_SEGMENT_SIZE       32768
#endif

/* send data from an in-memory buffer */
API_EXPORT(size_t) ap_send_mmap(void *mm, request_rec *r, size_t offset,
                             size_t length)
{
    size_t total_bytes_sent = 0;
    int n, w;

    if (length == 0)
        return 0;

    ap_soft_timeout("send mmap", r);

    length += offset;
    while (!r->connection->aborted && offset < length) {
        if (length - offset > MMAP_SEGMENT_SIZE) {
            n = MMAP_SEGMENT_SIZE;
        }
        else {
            n = length - offset;
        }

        while (n && !r->connection->aborted) {
            w = ap_bwrite(r->connection->client, (char *) mm + offset, n);
            if (w > 0) {
                ap_reset_timeout(r);   /* reset timeout after successful write */
                total_bytes_sent += w;
                n -= w;
                offset += w;
            }
            else if (w < 0) {
                if (r->connection->aborted)
                    break;
                else if (errno == EAGAIN)
                    continue;
                else {
                    ap_log_error(APLOG_MARK, APLOG_INFO, r->server,
                     "%s client stopped connection before send mmap completed",
                                ap_get_remote_host(r->connection,
                                                r->per_dir_config,
                                                REMOTE_NAME));
                    ap_bsetflag(r->connection->client, B_EOUT, 1);
                    r->connection->aborted = 1;
                    break;
                }
            }
        }
    }

    ap_kill_timeout(r);
    SET_BYTES_SENT(r);
    return total_bytes_sent;
}

API_EXPORT(int) ap_rputc(int c, request_rec *r)
{
    if (r->connection->aborted)
        return EOF;
    ap_bputc(c, r->connection->client);
    SET_BYTES_SENT(r);
    return c;
}

API_EXPORT(int) ap_rputs(const char *str, request_rec *r)
{
    int rcode;

    if (r->connection->aborted)
        return EOF;
    rcode = ap_bputs(str, r->connection->client);
    SET_BYTES_SENT(r);
    return rcode;
}

API_EXPORT(int) ap_rwrite(const void *buf, int nbyte, request_rec *r)
{
    int n;
    if (r->connection->aborted)
        return EOF;
    n = ap_bwrite(r->connection->client, buf, nbyte);
    SET_BYTES_SENT(r);
    return n;
}

API_EXPORT(int) ap_rprintf(request_rec *r, const char *fmt,...)
{
    va_list vlist;
    int n;

    if (r->connection->aborted)
        return EOF;
    va_start(vlist, fmt);
    n = ap_vbprintf(r->connection->client, fmt, vlist);
    va_end(vlist);
    SET_BYTES_SENT(r);
    return n;
}

API_EXPORT_NONSTD(int) ap_rvputs(request_rec *r,...)
{
    va_list args;
    int i, j, k;
    const char *x;
    BUFF *fb = r->connection->client;

    if (r->connection->aborted)
        return EOF;

    va_start(args, r);
    for (k = 0;;) {
        x = va_arg(args, const char *);
        if (x == NULL)
            break;
        j = strlen(x);
        i = ap_bwrite(fb, x, j);
        if (i != j) {
            va_end(args);
            return -1;
        }
        k += i;
    }
    va_end(args);

    SET_BYTES_SENT(r);
    return k;
}

API_EXPORT(int) ap_rflush(request_rec *r)
{
    return ap_bflush(r->connection->client);
}

/* We should have named this send_canned_response, since it is used for any
 * response that can be generated by the server from the request record.
 * This includes all 204 (no content), 3xx (redirect), 4xx (client error),
 * and 5xx (server error) messages that have not been redirected to another
 * handler via the ErrorDocument feature.
 */
void ap_send_error_response(request_rec *r, int recursive_error)
{
    BUFF *fd = r->connection->client;
    int status = r->status;
    int idx = ap_index_of_response(status);
    char *custom_response;
    const char *location = ap_table_get(r->headers_out, "Location");

    /* We need to special-case the handling of 204 and 304 responses,
     * since they have specific HTTP requirements and do not include a
     * message body.  Note that being assbackwards here is not an option.
     */
    if (status == HTTP_NOT_MODIFIED) {
        if (!ap_is_empty_table(r->err_headers_out))
            r->headers_out = ap_overlay_tables(r->pool, r->err_headers_out,
                                               r->headers_out);
        ap_hard_timeout("send 304", r);

        ap_basic_http_header(r);
        ap_set_keepalive(r);

        ap_table_do((int (*)(void *, const char *, const char *)) ap_send_header_field,
                    (void *) r, r->headers_out,
                    "Connection",
                    "Keep-Alive",
                    "ETag",
                    "Content-Location",
                    "Expires",
                    "Cache-Control",
                    "Vary",
                    "Warning",
                    "WWW-Authenticate",
                    "Proxy-Authenticate",
                    NULL);

        terminate_header(r->connection->client);

        ap_kill_timeout(r);
        return;
    }

    if (status == HTTP_NO_CONTENT) {
        ap_send_http_header(r);
        ap_finalize_request_protocol(r);
        return;
    }

    if (!r->assbackwards) {
        table *tmp = r->headers_out;

        /* For all HTTP/1.x responses for which we generate the message,
         * we need to avoid inheriting the "normal status" header fields
         * that may have been set by the request handler before the
         * error or redirect, except for Location on external redirects.
         */
        r->headers_out = r->err_headers_out;
        r->err_headers_out = tmp;
        ap_clear_table(r->err_headers_out);

        if (location && *location
            && (is_HTTP_REDIRECT(status) || status == HTTP_CREATED))
            ap_table_setn(r->headers_out, "Location", location);

        r->content_language = NULL;
        r->content_languages = NULL;
        r->content_encoding = NULL;
        r->clength = 0;
        r->content_type = "text/html";

        if ((status == METHOD_NOT_ALLOWED) || (status == NOT_IMPLEMENTED))
            ap_table_setn(r->headers_out, "Allow", make_allow(r));

        ap_send_http_header(r);

        if (r->header_only) {
            ap_finalize_request_protocol(r);
            return;
        }
    }

    ap_hard_timeout("send error body", r);

    if ((custom_response = ap_response_code_string(r, idx))) {
        /*
         * We have a custom response output. This should only be
         * a text-string to write back. But if the ErrorDocument
         * was a local redirect and the requested resource failed
         * for any reason, the custom_response will still hold the
         * redirect URL. We don't really want to output this URL
         * as a text message, so first check the custom response
         * string to ensure that it is a text-string (using the
         * same test used in ap_die(), i.e. does it start with a ").
         * If it doesn't, we've got a recursive error, so find
         * the original error and output that as well.
         */
        if (custom_response[0] == '\"') {
            ap_bputs(custom_response + 1, fd);
            ap_kill_timeout(r);
            ap_finalize_request_protocol(r);
            return;
        }
        /*
         * Redirect failed, so get back the original error
         */
        while (r->prev && (r->prev->status != HTTP_OK))
            r = r->prev;
    }
    {
        char *title = status_lines[idx];
        char *h1;
        const char *error_notes;

        /* Accept a status_line set by a module, but only if it begins
         * with the 3 digit status code
         */
        if (r->status_line != NULL
            && strlen(r->status_line) > 4       /* long enough */
            && ap_isdigit(r->status_line[0])
            && ap_isdigit(r->status_line[1])
            && ap_isdigit(r->status_line[2])
            && ap_isspace(r->status_line[3])
            && ap_isalnum(r->status_line[4])) {
            title = r->status_line;
        }

        /* folks decided they didn't want the error code in the H1 text */
        h1 = &title[4];

        ap_bvputs(fd,
                  "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n"
                  "<HTML><HEAD>\n<TITLE>", title,
                  "</TITLE>\n</HEAD><BODY>\n<H1>", h1, "</H1>\n",
                  NULL);

        if ((error_notes = ap_table_get(r->notes, "error-notes"))) {
            ap_bputs(error_notes, fd);
        }
        else
            switch (status) {
            case REDIRECT:
            case MOVED:
                ap_bvputs(fd, "The document has moved <A HREF=\"",
                          ap_escape_html(r->pool, location), "\">here</A>.<P>\n", NULL);
                break;
            case HTTP_SEE_OTHER:
                ap_bvputs(fd, "The answer to your request is located <A HREF=\"",
                          ap_escape_html(r->pool, location), "\">here</A>.<P>\n", NULL);
                break;
            case HTTP_USE_PROXY:
                ap_bvputs(fd, "This resource is only accessible through the proxy\n",
                ap_escape_html(r->pool, location), "<BR>\nYou will need to ",
                     "configure your client to use that proxy.<P>\n", NULL);
                break;
            case HTTP_PROXY_AUTHENTICATION_REQUIRED:
            case AUTH_REQUIRED:
                ap_bputs("This server could not verify that you\n", fd);
                ap_bputs("are authorized to access the document you\n", fd);
                ap_bputs("requested.  Either you supplied the wrong\n", fd);
                ap_bputs("credentials (e.g., bad password), or your\n", fd);
                ap_bputs("browser doesn't understand how to supply\n", fd);
                ap_bputs("the credentials required.<P>\n", fd);
                break;
            case BAD_REQUEST:
                ap_bputs("Your browser sent a request that\n", fd);
                ap_bputs("this server could not understand.<P>\n", fd);
                break;
            case HTTP_FORBIDDEN:
                ap_bvputs(fd, "You don't have permission to access ",
                  ap_escape_html(r->pool, r->uri), "\non this server.<P>\n",
                          NULL);
                break;
            case NOT_FOUND:
                ap_bvputs(fd, "The requested URL ", ap_escape_html(r->pool, r->uri),
                          " was not found on this server.<P>\n", NULL);
                break;
            case METHOD_NOT_ALLOWED:
                ap_bvputs(fd, "The requested method ", r->method, " is not allowed "
                          "for the URL ", ap_escape_html(r->pool, r->uri),
                          ".<P>\n", NULL);
                break;
            case NOT_ACCEPTABLE:
                ap_bvputs(fd,
                 "An appropriate representation of the requested resource ",
                          ap_escape_html(r->pool, r->uri),
                          " could not be found on this server.<P>\n", NULL);
                /* fall through */
            case MULTIPLE_CHOICES:
                {
                    const char *list;
                    if ((list = ap_table_get(r->notes, "variant-list")))
                        ap_bputs(list, fd);
                }
                break;
            case LENGTH_REQUIRED:
                ap_bvputs(fd, "A request of the requested method ", r->method,
                          " requires a valid Content-length.<P>\n", NULL);
                break;
            case PRECONDITION_FAILED:
                ap_bvputs(fd, "The precondition on the request for the URL ",
                ap_escape_html(r->pool, r->uri), " evaluated to false.<P>\n",
                          NULL);
                break;
            case NOT_IMPLEMENTED:
                ap_bvputs(fd, ap_escape_html(r->pool, r->method), " to ",
                          ap_escape_html(r->pool, r->uri), " not supported.<P>\n", NULL);
                break;
            case BAD_GATEWAY:
                ap_bputs("The proxy server received an invalid\015\012", fd);
                ap_bputs("response from an upstream server.<P>\015\012", fd);
                break;
            case VARIANT_ALSO_VARIES:
                ap_bvputs(fd, "A variant for the requested entity  ",
                          ap_escape_html(r->pool, r->uri), " is itself a ",
                          "transparently negotiable resource.<P>\n", NULL);
                break;
            case HTTP_REQUEST_TIME_OUT:
                ap_bputs("I'm tired of waiting for your request.\n", fd);
                break;
            case HTTP_GONE:
                ap_bvputs(fd, "The requested resource<BR>",
                          ap_escape_html(r->pool, r->uri),
                          "<BR>\nis no longer available on this server ",
                          "and there is no forwarding address.\n",
                  "Please remove all references to this resource.\n", NULL);
                break;
            case HTTP_REQUEST_ENTITY_TOO_LARGE:
                ap_bvputs(fd, "The requested resource<BR>",
                          ap_escape_html(r->pool, r->uri), "<BR>\n",
                          "does not allow request data with ", r->method,
                          " requests, or the amount of data provided in\n",
                          "the request exceeds the capacity limit.\n", NULL);
                break;
            case HTTP_REQUEST_URI_TOO_LARGE:
                ap_bputs("The requested URL's length exceeds the capacity\n", fd);
                ap_bputs("limit for this server.\n", fd);
                break;
            case HTTP_UNSUPPORTED_MEDIA_TYPE:
                ap_bputs("The supplied request data is not in a format\n", fd);
                ap_bputs("acceptable for processing by this resource.\n", fd);
                break;
            case HTTP_SERVICE_UNAVAILABLE:
                ap_bputs("The server is temporarily unable to service your\n", fd);
                ap_bputs("request due to maintenance downtime or capacity\n", fd);
                ap_bputs("problems. Please try again later.\n", fd);
                break;
            case HTTP_GATEWAY_TIME_OUT:
                ap_bputs("The proxy server did not receive a timely response\n", fd);
                ap_bputs("from the upstream server.<P>\n", fd);
                break;
            default:            /* HTTP_INTERNAL_SERVER_ERROR */
                ap_bputs("The server encountered an internal error or\n", fd);
                ap_bputs("misconfiguration and was unable to complete\n", fd);
                ap_bputs("your request.<P>\n", fd);
                ap_bputs("Please contact the server administrator,\n ", fd);
                ap_bputs(ap_escape_html(r->pool, r->server->server_admin), fd);
                ap_bputs(" and inform them of the time the error occurred,\n", fd);
                ap_bputs("and anything you might have done that may have\n", fd);
                ap_bputs("caused the error.<P>\n", fd);
                break;
            }

        if (recursive_error) {
            ap_bvputs(fd, "<P>Additionally, a ",
                      status_lines[ap_index_of_response(recursive_error)],
                      "\nerror was encountered while trying to use an "
                      "ErrorDocument to handle the request.\n", NULL);
        }
        ap_bputs(ap_psignature("<HR>\n", r), fd);
        ap_bputs("</BODY></HTML>\n", fd);
    }
    ap_kill_timeout(r);
    ap_finalize_request_protocol(r);
}
